package com.yellowframework.cloud.aspect;

import com.alibaba.fastjson.JSON;
import com.yellowframework.cloud.common.constant.Constants;
import com.yellowframework.cloud.config.RabbitmqConfig;
import com.yellowframework.cloud.domain.system.SysDetailLog;
import com.yellowframework.cloud.domain.system.SysOperateLog;
import com.yellowframework.cloud.domain.system.code.SysDetailLogCode;
import com.yellowframework.cloud.domain.system.request.SysDetailLogRequest;
import com.yellowframework.cloud.utils.Oauth2Utils;
import com.yellowframework.cloud.utils.StringUtils;
import com.yellowframework.cloud.utils.SystemUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;


/**
 * 系统操作日志切面<p>
 *    记录操作日志、操作异常日志
 */
@Slf4j
@Aspect
@Component
public class OperateLogAspect {

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 切入点：所有API（登录相关日志已另行记录）
     */
    @Pointcut("execution( * com.yellowframework.cloud.api..*.*(..)) && !execution( * com.yellowframework.cloud.api.security.AuthControllerApi.*(..))")
    public void log() {
    }

    /**
     * 记录操作日志
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("log()")
    public Object operateLog(ProceedingJoinPoint point) throws Throwable {
        long start = System.currentTimeMillis();
        Object o = point.proceed();
        long end = System.currentTimeMillis();
        saveOperateLog(point, SysOperateLog.builder().time(end - start).operateStatus(Constants.OPERATE_STATUS_SUCCESS).build());
        return o;
    }

    /**
     * 保存操作日志
     *
     * @param joinPoint
     * @param log
     */
    private void getOperateLog(JoinPoint joinPoint, SysOperateLog log) throws NoSuchMethodException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.setRequestMethod(request.getMethod());

        Class<?> clazz = joinPoint.getTarget().getClass();

        String methodName = joinPoint.getSignature().getName();

        String operateEvent = getOperateEvent(joinPoint, clazz, methodName);

        log.setOperateEvent(operateEvent);

        log.setOperateName(Oauth2Utils.getCurrentUsername());
        log.setOperateTime(new Date());
        log.setOperateUrl(request.getRequestURL().toString());

        String className = joinPoint.getTarget().getClass().getName();
        log.setMethodName(className + "." + methodName + "()");

        log.setParams(JSON.toJSONString(joinPoint.getArgs()));
    }

    private String getOperateEvent(JoinPoint joinPoint, Class<?> clazz, String methodName) throws NoSuchMethodException {
        final Optional<Class<?>> optionalClass = Arrays.stream(clazz.getInterfaces()).filter(o -> o.isAnnotationPresent(Api.class)).findFirst();
        if (!optionalClass.isPresent()) {
            return methodName;
        }

        Api annotation = optionalClass.get().getAnnotation(Api.class);
        String name = StringUtils.isEmpty(annotation.value()) ? annotation.tags()[0] : annotation.value();
        Object[] args = joinPoint.getArgs();
        Class<?>[] classes = new Class<?>[args.length];
        for (int i = 0; i < args.length; i++) {
            classes[i] = args[i].getClass();
        }

        final Method method = optionalClass.get().getMethod(methodName, classes);
        if (!method.isAnnotationPresent(ApiOperation.class)) {
            return name + "-" + methodName;
        }

        final ApiOperation operation = method.getAnnotation(ApiOperation.class);
        return name + "-" + operation.value();
    }

    /**
     * 保存操作日志
     *
     * @param log
     */
    private void saveOperateLog(JoinPoint joinPoint, SysOperateLog log) throws NoSuchMethodException {
        getOperateLog(joinPoint, log);
        // TODO 发送消息，mq软件崩了，暂时注释
//        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM, RabbitmqConfig.ROUTINGKEY_OPERATE_LOG, log);
    }

    /**
     * 记录异常日志
     * @param point
     * @param operation
     * @param e
     * @return void
     * @author zhouhao
     * @date  2021/4/23 10:22
     */
    @AfterThrowing(pointcut = "log() && @annotation(operation)", throwing = "e")
    public void logAfterThrowing(JoinPoint point, ApiOperation operation, Throwable e) throws NoSuchMethodException {
        SysOperateLog log = SysOperateLog.builder().operateStatus(Constants.OPERATE_STATUS_FAIL).build();
        getOperateLog(point, log);

        final SysDetailLog detailLog = SysDetailLog.builder()
                .logId(log.getId())
                .logType(Integer.parseInt(SysDetailLogCode.LOG_TYPE_2.k()))
                .detail(SystemUtils.getStackTrace(e)).build();

        SysDetailLogRequest request = new SysDetailLogRequest();
        request.setSysOperateLog(log);
        request.setSysDetailLog(detailLog);

        // TODO 发送消息，mq软件崩了，暂时注释
//        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM, RabbitmqConfig.ROUTINGKEY_DETAIL_LOG, request);
    }
}
